package partition;

import network.Network;
import network.Port;
import verifier.Ports.Ports;
import verifier.util.BDDEngine;
import verifier.Ports.PersistentPorts;

import java.util.*;

class PortsLinkedList {
    public PersistentPorts ports;
    public PortsLinkedList ancestor;

    public PortsLinkedList(PersistentPorts ports, PortsLinkedList ancestor) {
        this.ports = ports;
        this.ancestor = ancestor;
    }
}

public class Aggregator {
    private HashMap<String, Partition> nameToPartition;
    private Collection<SubgraphPartition> partitions;
    private BDDEngine bddEngine;
    private HashSet<Integer> resultHs;
    public long bddTransferTime = 0;
    public int transferCnt = 0;
    public long bddOpTime = 0;
    public Network network;
    int cnt = 0;
    private final ArrayList<HashMap<Integer, PersistentPorts>> compToPredToPorts;
    private HashMap<Port, HashSet<Integer>> networkModel;

    private HashMap<Integer, PortsLinkedList> predToPortsLinkedList;

    public Aggregator(Collection<SubgraphPartition> partitions) {
        this.partitions = partitions;
        this.bddEngine = partitions.stream().findFirst().get().getBddEngine();
        this.compToPredToPorts = new ArrayList<>();
        this.predToPortsLinkedList = new HashMap<>();

    }

    public HashMap<Port, HashSet<Integer>> getNetworkModel() {
//        if (this.networkModel != null) return networkModel;
        long s = System.nanoTime();
        for (SubgraphPartition partition : partitions) {
            if (partition.getVerifier() == null) continue; // for mini-test, skip partition that didn't run

            HashMap<Integer, PersistentPorts> predToPorts = new HashMap<>();
            /*
            for (Map.Entry<PersistentPorts, Integer> entry : partition.getVerifier().portsToPredicate.entrySet()) {
                predToPorts.put(entry.getValue(), entry.getKey());
            }
             */
            compToPredToPorts.add(predToPorts);
        }

        this.networkModel = new HashMap<>();
        HashMap<Integer, PortsLinkedList> predicates = null;

        for (HashMap<Integer, PersistentPorts> comp : compToPredToPorts) {
            if (predicates == null) {
                predicates = new HashMap<>();
                for (Map.Entry<Integer, PersistentPorts> entry : comp.entrySet()) {
                    predicates.put(entry.getKey(), new PortsLinkedList(entry.getValue(), null));
                }
            } else {
                HashMap<Integer, PortsLinkedList> newPredicates = new HashMap<>();

                for (Map.Entry<Integer, PortsLinkedList> pred : predicates.entrySet()) {
                    int t = pred.getKey();

                    if (comp.containsKey(t)) { // if exists \rho(s) = t (isomorphism)
                        newPredicates.put(t, new PortsLinkedList(comp.get(t), pred.getValue()));

                        int tmp = t;
                        t = BDDEngine.BDDFalse;
                        bddEngine.deRef(tmp);
                    }

                    for (Map.Entry<Integer, PersistentPorts> entry : comp.entrySet()) {
                        int intersection = bddEngine.and(t, entry.getKey());

                        if (intersection != BDDEngine.BDDFalse) {
                            int tmp = t;
                            t = bddEngine.diff(t, intersection);
                            bddEngine.deRef(tmp);

                            newPredicates.put(intersection, new PortsLinkedList(entry.getValue(), pred.getValue()));
                        }
                    }

                    // The union of all predicates from a component must be the BDDTrue
                    if (t != BDDEngine.BDDFalse) System.out.println("Error: undefined predicate");
                }
                predicates = newPredicates;
            }
        }

        assert predicates != null;
        System.out.println("Total # predicates " + predicates.size());
        System.out.println("$merge bdds: " + (System.nanoTime() - s));
        s = System.nanoTime();
        for (Map.Entry<Integer, PortsLinkedList> entry : predicates.entrySet()) {
            int pred = entry.getKey();
            for (PortsLinkedList t = entry.getValue(); t != null; t = t.ancestor) {
                for (Port port : t.ports.getAll()) {
                    this.networkModel.putIfAbsent(port, new HashSet<>());
                    this.networkModel.get(port).add(pred);
                }
            }
        }
        this.predToPortsLinkedList = predicates;
        System.out.println("$extract model: " + (System.nanoTime() - s));
        return this.networkModel;
    }
}